package core.generator;

import core.SemanticConstants;

public class Skeletons {

	/**
	 * Instância única do objeto.
	 */
	private static Skeletons __instance;
	
	private String code;
	private Integer line;

	private String branchEnd;

	private String branchBegin;
	
	private Skeletons() {
		code = "";
		line = 0;
	}
	public static Skeletons getInstance() {

		if (Skeletons.__instance == null)
			Skeletons.__instance = new Skeletons();

		return Skeletons.__instance;
	}
	
	public void saveCodeToFile() throws InterruptedException {
		System.err.println("<<INICIO>>\n" + code + "<<FIM>>\n");
	}
	
	public void AtrConstSkel(String label, String reg1, String varExt, int value) {
		
		String spaces = "";
		if (label != null){
			code += label + ": ";
			spaces = "   ";
		}
		code += String.format("%s: LD %s, %s\n" +
				spaces + "ST %s, %s\n", 
				label, reg1, value, varExt, reg1);
		line += 2;
	}
	
	public void AtrVarSkel(String reg1, String varExt, String var1) {
		code +=  String.format("LD %s, %s\n" +
				"ST %s, %s\n", 
				reg1, var1, varExt, reg1);
		line += 2;
	}
	
	public void AtrArrayDirSkel(String reg1, String reg2, String var, String varExt, Integer index) {
		code +=  String.format("LD %s, %d\n" +
				"MUL %s, %s, 8\n" +
				"LD %s, %s(%s)\n" +
				"ST %s, %s\n", 
				reg1, index, reg1, reg1, reg2, "Arr("+Skeletons.getInstance().getLine()+")", reg1, varExt, reg2);
		line += 4;
	}
	
	public void AtrArrayEsqSkel(String reg1, String reg2, String var, String varExt, Integer index) {
		code +=  String.format("LD %s, %s\n" +
				"LD %s, %d\n" +
				"MUL %s, %s, 8\n" +
				"ST %s(%s), %s\n", 
				reg1, var, reg2, index, reg2, reg2, "Arr("+Skeletons.getInstance().getLine()+")", reg2, reg1);
		line += 4;
	}

	public void AddOpSkel(String label, String reg1, String reg2, String varExt, String var1, String var2) {
		
		String spaces = "";
		if (label != null){
			code += label + ": ";
			spaces = "   ";
		}
		code += String.format("LD %s, %s\n" +
				spaces + "LD %s, %s\n" +
				spaces + "ADD %s, %s, %s\n" +
				spaces + "ST %s, %s\n", 
				reg1, var1, reg2, var2, reg1, reg1, reg2, varExt, reg1);
		line += 4;
	}
	
	public void SubOpSkel(String reg1, String reg2, String varExt, String var1, String var2) {
		code += String.format("LD %s, %s\n" +
				"LD %s, %s\n" +
				"SUB %s, %s, %s\n" +
				"ST %s, %s\n", 
				reg1, var1, reg2, var2, reg1, reg1, reg2, varExt, reg1);
		line += 4;
	}
	
	public void MulOpSkel(String reg1, String reg2, String varExt, String var1, String var2) {
		code += String.format("LD %s, %s\n" +
				"LD %s, %s\n" +
				"MUL %s, %s, %s\n" +
				"ST %s, %s\n", 
				reg1, var1, reg2, var2, reg1, reg1, reg2, varExt, reg1);
		line += 4;
	}
	
	public void DivOpSkel(String reg1, String reg2, String varExt, String var1, String var2) {
		code += String.format("LD %s, %s\n" +
				"LD %s, %s\n" +
				"DIV %s, %s, %s\n" +
				"ST %s, %s\n", 
				reg1, var1, reg2, var2, reg1, reg1, reg2, varExt, reg1);
		line += 4;
	}
	
	public void ArrayRightSkel(String reg1, String reg2, String varExt, String index, String array, int size) {
		code += String.format("LD %s, %s\n" +
				"MUL %s, %s, %d\n" +
				"LD %s, %s(%s)\n" +
				"ST %s, %s\n", 
				reg1, index, reg1, reg1, size, reg2, array, reg1, varExt, reg2);
		line += 4;
	}
	
	public void ArrayLeftSkel(String reg1, String reg2, String var1, String index, String array, int size) {
		code += String.format("LD %s, %s\n" +
				"LD %s, %s\n" +
				"MUL %s, %s, %d\n" +
				"ST %s(%s), %s\n", 
				reg1, var1, reg2, index, reg2, reg2, size, array, reg2, reg1);
		line += 4;
	}
	
	public void CondLessThenSkel(String reg1, String reg2, String var1, String var2, String label) {
		code += String.format("LD %s, %s\n" +
				"LD %s, %s\n" +
				"SUB %s, %s, %s\n" +
				"BLTZ %s, %s\n", 
				reg1, var1, reg2, var2, reg1, reg1, reg2, reg1, label);
		line += 4;
	}
	
	public void CondLessEqualSkel(String reg1, String reg2, String var1, String var2, String label) {
		code += String.format("LD %s, %s\n" +
				"LD %s, %s\n" +
				"SUB %s, %s, %s\n" +
				"BLEZ %s, %s\n",
				reg1, var1, reg2, var2, reg1, reg1, reg2, reg1, label);
		line += 4;
	}
	
	public void CondEqualSkel(String reg1, String reg2, String var1, String var2, String label) {
		code += String.format("LD %s, %s\n" +
				"LD %s, %s\n" +
				"BEQ %s, %s, %s\n", 
				reg1, var1, reg2, var2, reg1, reg2, label);
		line += 3;
	}
	
	public void CondNotEqualSkel(String reg1, String reg2, String var1, String var2, String label) {
		code += String.format("LD %s, %s\n" +
				"LD %s, %s\n" +
				"BNE %s, %s, %s\n", 
				reg1, var1, reg2, var2, reg1, reg2, label);
		line += 3;
	}
	
	public Integer getLine() {
		return line;
	}
	
	public void ChooseRelation(String rel, String reg1, String reg2, String var1, String var2, String label){
		if (rel.equals(SemanticConstants.REL_EQ)) {
			CondEqualSkel(reg1, reg2, var1, var2, label);
		} else if (rel.equals(SemanticConstants.REL_LT)) {
			CondLessThenSkel(reg1, reg2, var1, var2, label);
		} else if (rel.equals(SemanticConstants.REL_GT)) {
			CondLessThenSkel(reg1, reg2, var2, var1, label);
		} else if (rel.equals(SemanticConstants.REL_LE)) {
			CondLessEqualSkel(reg1, reg2, var1, var2, label);
		} else if (rel.equals(SemanticConstants.REL_GE)) {
			CondLessEqualSkel(reg1, reg2, var2, var1, label);
		} else if (rel.equals(SemanticConstants.REL_NE)) {
			CondNotEqualSkel(reg1, reg2, var1, var2, label);
		}
	}
	
	public void addFakeProcedure(String procedureName) {
		code += String.format("actionsFor%s\n" +
				"BR *0(SP)\n", procedureName);
		line += 2;
	}
	
	public void addBranchToSP() {
		code += String.format("BR *0(SP)\n");
		line += 1;
	}
	
	public void addForBranch(String labelBegin, String labelEnd, String exp1, String exp2, String exp3, String reg1, String reg2) {
		code += String.format("LD %s, %s\n" +
				"LD %s, %s\n" +
				"%s: SUB %s, %s, %s\n" +
				"ADD %s, %s, %s\n" +
				"BLTZ %s, %s\n",
				reg1, exp1, reg2, exp2, labelBegin, reg2, reg2, reg1, reg1, reg1, exp3, reg2, labelEnd);
		line += 5;
		
		branchBegin = labelBegin;
		branchEnd = labelEnd;
	}
	
	public void addForBranchEnd(){
		code += String.format("BR %s\n" +
				"%s: ", branchBegin, branchEnd);
		line += 1;
	}
	
	public void addHalt() {
		code += String.format("halt\n");
		line += 1;
	}
	
	public void addBranchToSP(String lastLoopOutLabel) {
		code += String.format("BR %s\n", lastLoopOutLabel);
		line += 1;
	}
	
    public void addBranchToProcedure(String procsize, Integer procedureLine) {
    	code += String.format("ADD SP, SP, %s\n" +
				"ST *SP, #%d\n" +
				"BR %d\n" +
				"SUB SP, SP, %s\n", 
				procsize, this.line + 4, procedureLine, procsize);
		line += 4;
    }
    
    public void startSPRegisterStack(Integer stackStart) {
    	code += String.format("LD SP, %d\n", 
    			stackStart);
		line += 1;
    }
    
	public void AritmeticOperation(String rel, String reg1, String reg2, String varExt, String var1, String var2, String label){
		if (rel.equals(SemanticConstants.OP_PLUS)) {
			this.AddOpSkel(label, reg1, reg2, varExt, var1, var2);
		} else if (rel.equals(SemanticConstants.OP_MINUS)) {
			this.SubOpSkel(reg1, reg2, varExt, var1, var2);
		} else if (rel.equals(SemanticConstants.OP_TIMES)) {
			this.MulOpSkel(reg1, reg2, varExt, var1, var2);
		} else if (rel.equals(SemanticConstants.OP_DIV) || rel.equals(SemanticConstants.OP_DIVISION)) {
			this.DivOpSkel(reg1, reg2, varExt, var1, var2);
		}
	}
}
